package com.wang.tool.extension;

import com.wang.tool.util.CommonUtil;
import com.wang.tool.util.SimpleLogUtil;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.context.annotation.ScannedGenericBeanDefinition;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.util.ClassUtils;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 扩展点注册器
 *
 * @Author wangshaopeng
 * @Date 2022/5/15
 */
public class ExtensionRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(EnableExtension.class.getName(), false);
        if (annotationAttributes == null) {
            SimpleLogUtil.warn("[EnableExtensionFail] notFind annotationAttributes");
            return;
        }

        //注入扩展工厂
        String factoryName = buildBeanName(ExtensionFactory.class);
        if (registry.containsBeanDefinition(factoryName)) {
            //不是自己类
            BeanDefinition exist = registry.getBeanDefinition(factoryName);
            if (!ExtensionFactory.class.getName().equals(exist.getBeanClassName())) {
                throw new IllegalArgumentException("springContext already has beanName:" + factoryName + ", type:" + exist.getBeanClassName() + ", can not registry " + ExtensionFactory.class.getName());
            }
        } else {
            registry.registerBeanDefinition(factoryName, BeanDefinitionBuilder.rootBeanDefinition(ExtensionFactory.class).getBeanDefinition());
            SimpleLogUtil.info("[registerExtensionFactoryBean] factoryName:{0}", factoryName);
        }

        //注入指定扩展点
        String[] scanPackages = (String[]) annotationAttributes.get("scanPackages");
        ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false) {
            @Override
            protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
                return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
            }
        };

        provider.addIncludeFilter(new AnnotationTypeFilter(Extension.class));
        List<Class<?>> extensionTypes = new ArrayList<>();

        for (String basePackage : scanPackages) {
            for (BeanDefinition candidate : provider.findCandidateComponents(basePackage)) {
                try {
                    extensionTypes.add(Class.forName(candidate.getBeanClassName()));
                } catch (Throwable e) {
                    throw new RuntimeException(e);
                }
            }
        }

        for (Class<?> extensionType : extensionTypes.stream().distinct().collect(Collectors.toList())) {
            Extension extensionAnnotation = extensionType.getAnnotation(Extension.class);
            if (extensionAnnotation == null) {
                throw new IllegalArgumentException("extensionType:" + extensionType.getName() + " must has annotation @" + Extension.class.getName());
            }

            Class<? extends ExtensionSelector> selectorType = extensionAnnotation.selector();
            if (extensionType.equals(ExtensionSelector.class)) {
                throw new IllegalArgumentException("selectorType can not be " + ExtensionSelector.class.getName());
            }

            String selectorBeanName = buildBeanName(selectorType);
            if (registry.containsBeanDefinition(selectorBeanName)) {
                //不是自己类
                BeanDefinition exist = registry.getBeanDefinition(selectorBeanName);
                if (!selectorType.getName().equals(exist.getBeanClassName())) {
                    throw new IllegalArgumentException("springContext already has beanName:" + selectorType + ", type:" + exist.getBeanClassName() + ", but not be selectorType" + selectorType.getName() + "'s , please change this");
                }
            }else{
                BeanDefinitionBuilder definition = BeanDefinitionBuilder.rootBeanDefinition(selectorType);
                registry.registerBeanDefinition(selectorBeanName, definition.getBeanDefinition());
            }

            String beanName = buildBeanName(extensionType);
            if (registry.containsBeanDefinition(beanName)) {
                //不是自己类
                BeanDefinition exist = registry.getBeanDefinition(beanName);
                if (!ExtensionFactoryBean.class.getName().equals(exist.getBeanClassName())) {
                    throw new IllegalArgumentException("springContext already has beanName:" + beanName + ", type:" + exist.getBeanClassName() + ", can not registry extensionType:" + extensionType.getName() + "'s " + ExtensionFactoryBean.class.getName() + ", please change this");
                }
            }


            BeanDefinitionBuilder definition = BeanDefinitionBuilder.rootBeanDefinition(ExtensionFactoryBean.class);
            definition.addConstructorArgValue(extensionType);
            definition.addConstructorArgReference(factoryName);
            definition.addConstructorArgValue(selectorType);
            registry.registerBeanDefinition(beanName, definition.getBeanDefinition());




            SimpleLogUtil.info("[registerExtensionImplBean] extensionType:{0}, beanName:{1}", extensionType.getName(), beanName);
        }
    }

    private static String buildBeanName(Class<?> type) {
        String simpleName = type.getSimpleName();
        //首字母小写
        return Character.toLowerCase(simpleName.charAt(0)) + simpleName.substring(1);
    }


    public static class ExtensionFactoryBean<T> implements FactoryBean<T>, ApplicationContextAware {

        private Class<T> extensionType;
        private ExtensionFactory factory;
        private Class<? extends ExtensionSelector> selectorType;

        public ExtensionFactoryBean(Class<T> extensionType, ExtensionFactory factory, Class<? extends ExtensionSelector> selectorType) {
            this.extensionType = extensionType;
            this.factory = factory;
            this.selectorType = selectorType;
        }

        @Override
        public T getObject() {
            return factory.getExtensionProxy(extensionType);
        }

        @Override
        public Class<?> getObjectType() {
            return extensionType;
        }

        @Override
        public boolean isSingleton() {
            return true;
        }


        @Override
        public void setApplicationContext(ApplicationContext app) throws BeansException {
            //声明扩展点
            if (factory.hasExtension(extensionType)) {
                SimpleLogUtil.warn("[registerExtensionFactoryBeanIsExist] extensionType:{0}", extensionType.getName());
                return;
            }

//            String beanName = buildBeanName(selectorType);
            ExtensionSelector selector = app.getBean(selectorType);
            //发布扩展
            factory.publishExtension(extensionType, selector);
            //安装实现类
            Map<String, T> extensions = app.getBeansOfType(extensionType);
            if (CommonUtil.isNotEmpty(extensions)) {
                //把自己移除掉
                extensions.remove(buildBeanName(extensionType));
                extensions.values().forEach(impl -> {
                    ExtensionImpl implAnnotation = AnnotationUtils.findAnnotation(impl.getClass(), ExtensionImpl.class);
                    if (implAnnotation == null) {
                        SimpleLogUtil.warn("[tryInstallExtensionImpl] obj:{0}, but not has annotation @{1}, continue this;", impl, ExtensionImpl.class.getName());
                        return;
                    }
                    String name = implAnnotation.value();
                    factory.installExtensionImpl(extensionType, name, impl);
                });
            }
        }

        public Class<T> getExtensionType() {
            return extensionType;
        }

        public void setExtensionType(Class<T> extensionType) {
            this.extensionType = extensionType;
        }

        public ExtensionFactory getFactory() {
            return factory;
        }

        public void setFactory(ExtensionFactory factory) {
            this.factory = factory;
        }
    }


}
